/*-------------------------------------------------------------------------
 *
 * genfile.c
 *		用于直接访问文件的函数
 *
 *
 * Copyright (c) 2004-2022, PostgreSQL Global Development Group
 *
 * Author: Andreas Pflug <pgadmin@pse-consulting.de>
 *
 * IDENTIFICATION
 *	  src/backend/utils/adt/genfile.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <sys/file.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>

#include "access/htup_details.h"
#include "access/xlog_internal.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_tablespace_d.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "postmaster/syslogger.h"
#include "replication/slot.h"
#include "storage/fd.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/memutils.h"
#include "utils/syscache.h"
#include "utils/timestamp.h"


/*
 * 将“文本”文件名参数转换为 C 字符串，并检查其可行性。
 *
 * 文件名可以是绝对的或相对于 DataDir 的，但我们只允许
 * 与 DataDir 或 Log_directory 匹配的绝对路径。
 *
 * 这会对'pg_read_server_files'角色进行权限检查，因此
 * 此函数实际上只适用于仅检查“读取”访问的调用者。
 * 如果您要检查“写入”或“程序”访问，请勿使用此函数，
 * 而应将访问类型作为参数更新到检查中，并检查适当的角色成员资格。
 */
static char * fc_convert_and_check_filename(text *fc_arg)
{
	char	   *fc_filename;

	fc_filename = text_to_cstring(fc_arg);
	canonicalize_path(fc_filename);	/* 文件名在这里可以变化长度 */

	/*
	 * 具有 'pg_read_server_files' 角色权限的角色被允许
	 * 作为 PG 用户访问服务器上的任何文件，因此这里无需进行
	 * 进一步检查。
	 */
	if (has_privs_of_role(GetUserId(), ROLE_PG_READ_SERVER_FILES))
		return fc_filename;

	/*
	 * 用户不是 pg_read_server_files 角色的成员，因此检查其
	 * 可行性
	 */
	if (is_absolute_path(fc_filename))
	{
		/*
		 * 如果在 DataDir 或 Log_directory 内允许绝对路径，
		 * 即使 Log_directory 可能位于 DataDir 之外。
		 */
		if (!path_is_prefix_of_path(DataDir, fc_filename) &&
			(!is_absolute_path(Log_directory) ||
			 !path_is_prefix_of_path(Log_directory, fc_filename)))
			ereport(ERROR,
					(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
					 errmsg("absolute path not allowed")));
	}
	else if (!path_is_relative_and_below_cwd(fc_filename))
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("path must be in or below the current directory")));

	return fc_filename;
}


/*
 * 阅读文件的一部分，将其作为 bytea 返回
 *
 * 调用者负责所有权限检查。
 *
 * 当 bytes_to_read 为负时，我们读取整个文件。
 */
static bytea * fc_read_binary_file(const char *fc_filename, int64 fc_seek_offset, int64 fc_bytes_to_read,
				 bool fc_missing_ok)
{
	bytea	   *fc_buf;
	size_t		fc_nbytes = 0;
	FILE	   *fc_file;

	/* 将请求大小限制为我们实际上可以交付的大小 */
	if (fc_bytes_to_read > (int64) (MaxAllocSize - VARHDRSZ))
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("requested length too large")));

	if ((fc_file = AllocateFile(fc_filename, PG_BINARY_R)) == NULL)
	{
		if (fc_missing_ok && errno == ENOENT)
			return NULL;
		else
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not open file \"%s\" for reading: %m",
							fc_filename)));
	}

	if (fseeko(fc_file, (off_t) fc_seek_offset,
			   (fc_seek_offset >= 0) ? SEEK_SET : SEEK_END) != 0)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not seek in file \"%s\": %m", fc_filename)));

	if (fc_bytes_to_read >= 0)
	{
		/* 如果传递了显式读取大小，则直接执行 */
		fc_buf = (bytea *) palloc((Size) fc_bytes_to_read + VARHDRSZ);

		fc_nbytes = fread(VARDATA(fc_buf), 1, (size_t) fc_bytes_to_read, fc_file);
	}
	else
	{
		/* 负读取大小，读取文件的其余部分 */
		StringInfoData fc_sbuf;

		initStringInfo(&fc_sbuf);
		/* 在缓冲区留出 varlena 长度字的空间 */
		fc_sbuf.len += VARHDRSZ;
		Assert(fc_sbuf.len < fc_sbuf.maxlen);

		while (!(feof(fc_file) || ferror(fc_file)))
		{
			size_t		fc_rbytes;

			/* 每次读取的最小量 */
#define MIN_READ_SIZE 4096

			/*
			 * 如果未到文件末尾，并且 sbuf.len 等于 MaxAllocSize -
			 * 1，则文件要么太大，要么已没有可读内容。
			 * 尝试再读取一个字节以查看是否已到文件末尾。
			 * 如果没有，则文件太大；我们宁愿自己给出
			 * 错误信息。
			 */
			if (fc_sbuf.len == MaxAllocSize - 1)
			{
				char		fc_rbuf[1];

				if (fread(fc_rbuf, 1, 1, fc_file) != 0 || !feof(fc_file))
					ereport(ERROR,
							(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
							 errmsg("file length too large")));
				else
					break;
			}

			/* 好的，确保我们至少可以读取 MIN_READ_SIZE */
			enlargeStringInfo(&fc_sbuf, MIN_READ_SIZE);

			/*
			 * stringinfo.c 喜欢以2的幂次分配内存，因此很可能
			 * 可用的空间比我们请求的要多得多。 使用所有
			 * 的空间，而不是进行更多的 fread 调用。
			 */
			fc_rbytes = fread(fc_sbuf.data + fc_sbuf.len, 1,
						   (size_t) (fc_sbuf.maxlen - fc_sbuf.len - 1), fc_file);
			fc_sbuf.len += fc_rbytes;
			fc_nbytes += fc_rbytes;
		}

		/* 现在我们可以将 stringinfo 的缓冲区作为结果使用 */
		fc_buf = (bytea *) fc_sbuf.data;
	}

	if (ferror(fc_file))
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not read file \"%s\": %m", fc_filename)));

	SET_VARSIZE(fc_buf, fc_nbytes + VARHDRSZ);

	FreeFile(fc_file);

	return fc_buf;
}

/*
 * 类似于 read_binary_file，但我们验证内容在数据库编码中是有效的。
 */
static text * fc_read_text_file(const char *fc_filename, int64 fc_seek_offset, int64 fc_bytes_to_read,
			   bool fc_missing_ok)
{
	bytea	   *fc_buf;

	fc_buf = fc_read_binary_file(fc_filename, fc_seek_offset, fc_bytes_to_read, fc_missing_ok);

	if (fc_buf != NULL)
	{
		/* 确保输入是有效的 */
		pg_verifymbstr(VARDATA(fc_buf), VARSIZE(fc_buf) - VARHDRSZ, false);

		/* 好的，我们可以安全地将其转换为文本 */
		return (text *) fc_buf;
	}
	else
		return NULL;
}

/*
 * 读取文件的一部分，并将其作为文本返回
 *
 * 此功能保留是为了支持 adminpack 1.0。
 */
Datum pg_read_file(PG_FUNCTION_ARGS)
{
	text	   *fc_filename_t = PG_GETARG_TEXT_PP(0);
	int64		fc_seek_offset = 0;
	int64		fc_bytes_to_read = -1;
	bool		fc_missing_ok = false;
	char	   *fc_filename;
	text	   *fc_result;

	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to read files with adminpack 1.0"),
		/* 翻译者：%s 是 SQL 函数名称 */
				 errhint("Consider using %s, which is part of core, instead.",
						 "pg_read_file()")));

	/* 处理可选参数 */
	if (PG_NARGS() >= 3)
	{
		fc_seek_offset = PG_GETARG_INT64(1);
		fc_bytes_to_read = PG_GETARG_INT64(2);

		if (fc_bytes_to_read < 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("requested length cannot be negative")));
	}
	if (PG_NARGS() >= 4)
		fc_missing_ok = PG_GETARG_BOOL(3);

	fc_filename = fc_convert_and_check_filename(fc_filename_t);

	fc_result = fc_read_text_file(fc_filename, fc_seek_offset, fc_bytes_to_read, fc_missing_ok);
	if (fc_result)
		PG_RETURN_TEXT_P(fc_result);
	else
		PG_RETURN_NULL();
}

/*
 * 读取文件的一部分，并将其作为文本返回
 *
 * 这里没有进行超级用户检查 - 而是通过 GRANT 系统处理权限。
 */
Datum pg_read_file_v2(PG_FUNCTION_ARGS)
{
	text	   *fc_filename_t = PG_GETARG_TEXT_PP(0);
	int64		fc_seek_offset = 0;
	int64		fc_bytes_to_read = -1;
	bool		fc_missing_ok = false;
	char	   *fc_filename;
	text	   *fc_result;

	/* 处理可选参数 */
	if (PG_NARGS() >= 3)
	{
		fc_seek_offset = PG_GETARG_INT64(1);
		fc_bytes_to_read = PG_GETARG_INT64(2);

		if (fc_bytes_to_read < 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("requested length cannot be negative")));
	}
	if (PG_NARGS() >= 4)
		fc_missing_ok = PG_GETARG_BOOL(3);

	fc_filename = fc_convert_and_check_filename(fc_filename_t);

	fc_result = fc_read_text_file(fc_filename, fc_seek_offset, fc_bytes_to_read, fc_missing_ok);
	if (fc_result)
		PG_RETURN_TEXT_P(fc_result);
	else
		PG_RETURN_NULL();
}

/*
 * 读取文件的一部分，并将其作为 bytea 返回
 */
Datum pg_read_binary_file(PG_FUNCTION_ARGS)
{
	text	   *fc_filename_t = PG_GETARG_TEXT_PP(0);
	int64		fc_seek_offset = 0;
	int64		fc_bytes_to_read = -1;
	bool		fc_missing_ok = false;
	char	   *fc_filename;
	bytea	   *fc_result;

	/* 处理可选参数 */
	if (PG_NARGS() >= 3)
	{
		fc_seek_offset = PG_GETARG_INT64(1);
		fc_bytes_to_read = PG_GETARG_INT64(2);

		if (fc_bytes_to_read < 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("requested length cannot be negative")));
	}
	if (PG_NARGS() >= 4)
		fc_missing_ok = PG_GETARG_BOOL(3);

	fc_filename = fc_convert_and_check_filename(fc_filename_t);

	fc_result = fc_read_binary_file(fc_filename, fc_seek_offset,
							  fc_bytes_to_read, fc_missing_ok);
	if (fc_result)
		PG_RETURN_BYTEA_P(fc_result);
	else
		PG_RETURN_NULL();
}


/*
 * pg_read_file_v2() 和 pg_read_binary_file() 的 1 个和 3 个参数变体的包装函数。
 *
 * 这些是必要的，以通过 opr_sanity 中的完整性检查，该检查确保
 * 所有共享实现 C 函数的内置函数都使用相同数量的参数。
 */
Datum pg_read_file_off_len(PG_FUNCTION_ARGS)
{
	return pg_read_file_v2(fcinfo);
}

Datum pg_read_file_all(PG_FUNCTION_ARGS)
{
	return pg_read_file_v2(fcinfo);
}

Datum pg_read_binary_file_off_len(PG_FUNCTION_ARGS)
{
	return pg_read_binary_file(fcinfo);
}

Datum pg_read_binary_file_all(PG_FUNCTION_ARGS)
{
	return pg_read_binary_file(fcinfo);
}

/*
 * stat 一个文件
 */
Datum pg_stat_file(PG_FUNCTION_ARGS)
{
	text	   *fc_filename_t = PG_GETARG_TEXT_PP(0);
	char	   *fc_filename;
	struct stat fc_fst;
	Datum		fc_values[6];
	bool		fc_isnull[6];
	HeapTuple	fc_tuple;
	TupleDesc	fc_tupdesc;
	bool		fc_missing_ok = false;

	/* 检查可选参数 */
	if (PG_NARGS() == 2)
		fc_missing_ok = PG_GETARG_BOOL(1);

	fc_filename = fc_convert_and_check_filename(fc_filename_t);

	if (stat(fc_filename, &fc_fst) < 0)
	{
		if (fc_missing_ok && errno == ENOENT)
			PG_RETURN_NULL();
		else
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not stat file \"%s\": %m", fc_filename)));
	}

	/*
	 * 这个记录类型最好与我在 pg_proc.h 中声明的输出参数匹配。
	 */
	fc_tupdesc = CreateTemplateTupleDesc(6);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 1,
					   "size", INT8OID, -1, 0);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 2,
					   "access", TIMESTAMPTZOID, -1, 0);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 3,
					   "modification", TIMESTAMPTZOID, -1, 0);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 4,
					   "change", TIMESTAMPTZOID, -1, 0);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 5,
					   "creation", TIMESTAMPTZOID, -1, 0);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 6,
					   "isdir", BOOLOID, -1, 0);
	BlessTupleDesc(fc_tupdesc);

	memset(fc_isnull, false, sizeof(fc_isnull));

	fc_values[0] = Int64GetDatum((int64) fc_fst.st_size);
	fc_values[1] = TimestampTzGetDatum(time_t_to_timestamptz(fc_fst.st_atime));
	fc_values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fc_fst.st_mtime));
	/* Unix 有文件状态更改时间，而 Win32 有创建时间 */
#if !defined(WIN32) && !defined(__CYGWIN__)
	fc_values[3] = TimestampTzGetDatum(time_t_to_timestamptz(fc_fst.st_ctime));
	fc_isnull[4] = true;
#else
	fc_isnull[3] = true;
	fc_values[4] = TimestampTzGetDatum(time_t_to_timestamptz(fc_fst.st_ctime));
#endif
	fc_values[5] = BoolGetDatum(S_ISDIR(fc_fst.st_mode));

	fc_tuple = heap_form_tuple(fc_tupdesc, fc_values, fc_isnull);

	pfree(fc_filename);

	PG_RETURN_DATUM(HeapTupleGetDatum(fc_tuple));
}

/*
 * stat 一个文件（1 个参数版本）
 *
 * 注意：这个包装是必要的，以通过 opr_sanity 中的完整性检查，
 * 该检查确保所有共享实现 C 函数的内置函数都使用相同数量的参数。
 */
Datum pg_stat_file_1arg(PG_FUNCTION_ARGS)
{
	return pg_stat_file(fcinfo);
}

/*
 * 列出一个目录（仅返回文件名）
 */
Datum pg_ls_dir(PG_FUNCTION_ARGS)
{
	ReturnSetInfo *fc_rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
	char	   *fc_location;
	bool		fc_missing_ok = false;
	bool		fc_include_dot_dirs = false;
	DIR		   *fc_dirdesc;
	struct dirent *fc_de;

	fc_location = fc_convert_and_check_filename(PG_GETARG_TEXT_PP(0));

	/* 检查可选参数 */
	if (PG_NARGS() == 3)
	{
		if (!PG_ARGISNULL(1))
			fc_missing_ok = PG_GETARG_BOOL(1);
		if (!PG_ARGISNULL(2))
			fc_include_dot_dirs = PG_GETARG_BOOL(2);
	}

	InitMaterializedSRF(fcinfo, MAT_SRF_USE_EXPECTED_DESC);

	fc_dirdesc = AllocateDir(fc_location);
	if (!fc_dirdesc)
	{
		/* 如果合适，则返回空的 tuplestore */
		if (fc_missing_ok && errno == ENOENT)
			return (Datum) 0;
		/* 否则，我们可以让 ReadDir() 抛出错误 */
	}

	while ((fc_de = ReadDir(fc_dirdesc, fc_location)) != NULL)
	{
		Datum		fc_values[1];
		bool		fc_nulls[1];

		if (!fc_include_dot_dirs &&
			(strcmp(fc_de->d_name, ".") == 0 ||
			 strcmp(fc_de->d_name, "..") == 0))
			continue;

		fc_values[0] = CStringGetTextDatum(fc_de->d_name);
		fc_nulls[0] = false;

		tuplestore_putvalues(fc_rsinfo->setResult, fc_rsinfo->setDesc,
							 fc_values, fc_nulls);
	}

	FreeDir(fc_dirdesc);
	return (Datum) 0;
}

/*
 * 列出一个目录（1 个参数版本）
 *
 * 注意：这个包装是必要的，以通过 opr_sanity 中的完整性检查，
 * 该检查确保所有共享实现 C 函数的内置函数都使用相同数量的参数。
 */
Datum pg_ls_dir_1arg(PG_FUNCTION_ARGS)
{
	return pg_ls_dir(fcinfo);
}

/*
 * 通用函数以返回文件的目录列表。
 *
 * 如果目录不存在，如果 missing_ok 则静默返回空集。
 * 其他不可读目录的情况会抛出错误。
 */
static Datum pg_ls_dir_files(FunctionCallInfo fcinfo, const char *fc_dir, bool fc_missing_ok)
{
	ReturnSetInfo *fc_rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
	DIR		   *fc_dirdesc;
	struct dirent *fc_de;

	InitMaterializedSRF(fcinfo, 0);

	/*
	 * 现在遍历目录。 注意，我们必须在单个 SRF 调用内执行此操作，不能在多个调用中保持目录打开，因为我们
	 * 无法保证 SRF 会运行到完成。
	 */
	fc_dirdesc = AllocateDir(fc_dir);
	if (!fc_dirdesc)
	{
		/* 如果合适，则返回空的 tuplestore */
		if (fc_missing_ok && errno == ENOENT)
			return (Datum) 0;
		/* 否则，我们可以让 ReadDir() 抛出错误 */
	}

	while ((fc_de = ReadDir(fc_dirdesc, fc_dir)) != NULL)
	{
		Datum		fc_values[3];
		bool		fc_nulls[3];
		char		fc_path[MAXPGPATH * 2];
		struct stat fc_attrib;

		/* 跳过隐藏文件 */
		if (fc_de->d_name[0] == '.')
			continue;

		/* 获取文件信息 */
		snprintf(fc_path, sizeof(fc_path), "%s/%s", fc_dir, fc_de->d_name);
		if (stat(fc_path, &fc_attrib) < 0)
		{
			/* 忽略同时删除的文件，否则投诉 */
			if (errno == ENOENT)
				continue;
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not stat file \"%s\": %m", fc_path)));
		}

		/* 忽略除了常规文件之外的任何内容 */
		if (!S_ISREG(fc_attrib.st_mode))
			continue;

		fc_values[0] = CStringGetTextDatum(fc_de->d_name);
		fc_values[1] = Int64GetDatum((int64) fc_attrib.st_size);
		fc_values[2] = TimestampTzGetDatum(time_t_to_timestamptz(fc_attrib.st_mtime));
		memset(fc_nulls, 0, sizeof(fc_nulls));

		tuplestore_putvalues(fc_rsinfo->setResult, fc_rsinfo->setDesc, fc_values, fc_nulls);
	}

	FreeDir(fc_dirdesc);
	return (Datum) 0;
}

/* 返回日志目录中文件列表的函数 */
Datum pg_ls_logdir(PG_FUNCTION_ARGS)
{
	return pg_ls_dir_files(fcinfo, Log_directory, false);
}

/* 返回 WAL 目录中文件列表的函数 */
Datum pg_ls_waldir(PG_FUNCTION_ARGS)
{
	return pg_ls_dir_files(fcinfo, XLOGDIR, false);
}

/*
 * 通用函数以返回 pgsql_tmp 中的文件列表
 */
static Datum pg_ls_tmpdir(FunctionCallInfo fcinfo, Oid fc_tblspc)
{
	char		fc_path[MAXPGPATH];

	if (!SearchSysCacheExists1(TABLESPACEOID, ObjectIdGetDatum(fc_tblspc)))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("tablespace with OID %u does not exist",
						fc_tblspc)));

	TempTablespacePath(fc_path, fc_tblspc);
	return pg_ls_dir_files(fcinfo, fc_path, true);
}

/*
 * 返回 pg_default 表空间中的 pgsql_tmp 目录中的临时文件列表的函数
 */
Datum pg_ls_tmpdir_noargs(PG_FUNCTION_ARGS)
{
	return pg_ls_tmpdir(fcinfo, DEFAULTTABLESPACE_OID);
}

/*
 * 函数返回指定表空间的
 * pgsql_tmp 目录中的临时文件列表
 */
Datum pg_ls_tmpdir_1arg(PG_FUNCTION_ARGS)
{
	return pg_ls_tmpdir(fcinfo, PG_GETARG_OID(0));
}

/*
 * 函数返回 WAL 存档状态目录中的文件列表。
 */
Datum pg_ls_archive_statusdir(PG_FUNCTION_ARGS)
{
	return pg_ls_dir_files(fcinfo, XLOGDIR "/archive_status", true);
}

/*
 * 函数返回 pg_logical/snapshots 目录中的文件列表。
 */
Datum pg_ls_logicalsnapdir(PG_FUNCTION_ARGS)
{
	return pg_ls_dir_files(fcinfo, "pg_logical/snapshots", false);
}

/*
 * 函数返回 pg_logical/mappings 目录中的文件列表。
 */
Datum pg_ls_logicalmapdir(PG_FUNCTION_ARGS)
{
	return pg_ls_dir_files(fcinfo, "pg_logical/mappings", false);
}

/*
 * 函数返回 pg_replslot/<replication_slot>
 * 目录中的文件列表。
 */
Datum pg_ls_replslotdir(PG_FUNCTION_ARGS)
{
	text	   *fc_slotname_t;
	char		fc_path[MAXPGPATH];
	char	   *fc_slotname;

	fc_slotname_t = PG_GETARG_TEXT_PP(0);

	fc_slotname = text_to_cstring(fc_slotname_t);

	if (!SearchNamedReplicationSlot(fc_slotname, true))
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("replication slot \"%s\" does not exist",
						fc_slotname)));

	snprintf(fc_path, sizeof(fc_path), "pg_replslot/%s", fc_slotname);
	return pg_ls_dir_files(fcinfo, fc_path, false);
}
